Java Security(I) Reflection

Java安全可以从反序列化漏洞开始说起,反序列化漏洞⼜可以从反射开始说起。 ——phith0n

简介

对于java安全而言,反射是一个很重要的基础知识。我们可以通过对对象的反射来获得某一个Class,也可以通过对Class的反射获得他的所有方法(包括私有)。

在一些只有难以利用的基本类型对象(String, Char, Integer, ...)的情况下,利用反射获取到某些可以加以利用的Class 往往是一个不错的利用手段。

Exploitaion

反射中常用的方法

  • forName获取类

    • 参数需要是目标Class的全名
  • newInstance实例化一个对应Class的对象

    • 在目标类拥有无参构造函数的时候不需要参数即可使用
  • getMethod获取对应Class的某个方法

  • invoke执行方法

    • 一般来说至少会有一个参数,是使用这个方法的对象,后续参数是对于方法需要的参数

利用反射获取Class

  • 如果上文存在对应Class的对象obj,那么直接使用obj.getClass()方法即可。
  • 通常来说不会存在对应的对象,所以需要用到Class.forName("target-class-name")来获取到目标类

(其实如果已经加载了某个类,比如说Test,是可以直接使用Test.class来拿到这个类的,不过此方法不属于反射就是了。而且通过此方法来获取对某个类的引用的时候,是不会触发类加载器(ClassLoader)对他的初始化操作的,而通过forName来引用时则会默认触发,这事实上和forName方法的一个重载有关)

只是说的话比较枯燥,下面来看几个demo理解一下吧:

demo1-使用obj.getClass()

reflection类的内容如下(后续demo也会基于这个类来示范):

public class reflection {
    public void hello(){
        System.out.println("hello!");
    }
    private void secret(){
        System.out.println("You find me!!");
    }
}

现在我们要用反射获取到他的类,并使用他的hello方法。

import java.lang.reflect.Method;

public class test {
    public static void main(String[] args) throws  Exception {
        reflection a = new reflection();
        a.hello();
        Class ref = a.getClass();
        ref.getMethod("hello").invoke(a);
    }
}

由于hello方法是公有的,所以这里使用了两种方法来调用它。

demo2-使用forName()

在上下文没有reflection类的时候也能够调用他的方法。

import java.lang.reflect.Method;

public class test {
    public static void main(String[] args) throws  Exception {
        Class ref = Class.forName("reflection");
        ref.getMethod("hello").invoke(ref.newInstance());
    }
}

demo3-对私有方法的反射

reflection类中还有一个私有方法secret(),我们无法使用getMethod来获取到他。

此时就可以使用getDeclaredMethod方法强行获取,然后再用setAccessible(true)赋予执行权限。

import java.lang.reflect.Method;

public class test {
    public static void main(String[] args) throws  Exception {
        Class ref = Class.forName("reflection");
        Method secr = ref.getDeclaredMethod("secret");
        secr.setAccessible(true);
        secr.invoke(ref.newInstance());
    }
}

(我这里使用的是jdk 8u341,此方法在高版本可能并不适用)

浅谈利用反射的RCE思路

使用forName反射java.lang.Runtime类

java.lang.Runtime类中有exec方法,可以直接使用一些系统命令。

不过要注意的是java.lang.Runtime类在设计上使用的是单例模式,我们想使用invoke的时候还需要传一个该类型的对象进去,此时newInstance方法是无法使用的。所以还需要先反射得到getRuntime这个方法来获取一个对象。

import java.lang.reflect.Method;

public class test {
    public static void main(String[] args) throws  Exception {
        Class runtime = Class.forName("java.lang.Runtime");
        Method getruntime = runtime.getMethod("getRuntime");
        Object obj = getruntime.invoke(runtime);
        runtime.getMethod("exec", String.class).invoke(obj, "calc.exe");
    }
}

运行成功将弹出计算器。

1661870901546.png

无默认无参构造函数下的利用

java.lang.Runtime类的情况也是无法使用newInstance的,此时我们可以用 getConstructor来反射。

getMethod类似, getConstructor 接收的参数是构造函数列表类型,因为构造函数也支持重载,
所以必须用参数列表类型才能唯一确定一个构造函数。

此外,java.lang.ProcessBuilder也是一种常用于RCE的函数,这里用它做一个示范

import java.lang.reflect.Method;

public class test {
    public static void main(String[] args) throws  Exception {
        Class pb = Class.forName("java.lang.ProcessBuilder");
        ((ProcessBuilder)pb.getConstructor(String[].class).newInstance(new String[][]{{"calc.exe"}})).start();
    }
}
1661871587238.png